名字裡究竟有什麼?如果玫瑰不叫玫瑰,它還是一樣的芳香。
-- 莎士比亞, 哈姆雷特
雨,開始下起來了。空氣中滿是潮溼的香氣,隨著雨滴打在道路及建築上的聲音漸響,萬事萬物,開始靜默下來。
無視雨打進窗沿,坐在我對面的那隻小動物,依然悠悠的一邊晃著尾巴,並在桌上刻出各種範例,一邊講訴著故事…
但是在這個 JS 莊園裡,從最初開始,函式就是一等公民。設計者喜歡他曾待過的 LISP 群島中的 Scheme 島。於是在設計這個莊園時,就將這個傳統帶進來了。在這裡,函式可以指派給變數:
let cmp = function(i1, i2) { return i2 - i1 }
接下來就可以把這個函式,直接傳給sort
函式做為參數:
[1, 4, 3, 6, 5, 2, 7, 8].sort(cmp) //=> [8, 7, 6, 5, 4, 3, 2, 1]
當然,如果沒有其它地方需要用到那個 cmp
函式的話,你也可以不要指派變數,而是在呼叫 sort
函式時,直接把匿名函式傳遞進去:
[1, 4, 3, 6, 5, 2, 7, 8].sort(function(i1, i2) { return i2 - i1 }) //=> [8, 7, 6, 5, 4, 3, 2, 1]
而在上一次莊園都更的時候,更引進一種新的函式的鍊成陣。你可以用這種方式做出匿名函式:
function (i1, i2) { return i2 - i1 } // 把這個
(i1, i2) => i2 - i1 // 改寫成這樣
數列的排序也可以改寫成這樣:
[1, 4, 3, 6, 5, 2, 7, 8].sort((i1, i2) => i2 - i1) //=> [8, 7, 6, 5, 4, 3, 2, 1]
你有沒有注意到,我們今天畫出來的函式鍊成陣,跟我們昨天畫出來的函式,除了內容之外,還有個地方不太一樣?
function name(x) { /* 做一些事 */ } //之前的
function (x) { /* 做一些事 */ } //今天的
「少了那個 name
,少了…名字」。
沒錯!函式有名稱這個屬性。但既然它是自由的,它也可以不要有名字。事實上,在許多不同地方,對於這種沒有名字,且是一等公民的函式,有一種共同的稱呼,叫做:「嵐達 (lambda)」。而既然它沒有名字,也有人把它稱為匿名函式。
也有許多魔法師,認為它的真名,是一個符號:
我們再來試個複雜一點的術法,首先假設我們有這樣的兩個術式,有看到它們之間類似的地方嗎?
// 第一段
putInPot("beef")
putInPot("soup")
console.log("beef soup is done!")
// 第二段
boomboom("chicken")
boomboom("coconut")
console.log("chicken coconut is done!")
// 被呼叫的函式定義在這
function putInPot(igr) {console.log(igr + " in the pot!")}
function boomboom(igr) {console.log("boil the " + igr + "!")}
「兩個的過程很像,都是呼叫兩次函式,然後用 console.log
印一段使用了同樣參數的句子。」
是的,那我們有辦法再繼續簡化這兩段術式嗎?
過去的 JAVA 王國魔法師會認為這段魔法沒有辦法再更簡潔了,或是要動用到鏡反射 (reflection) 等麻煩的東西才搞得定。
但是正如你說的,施法的過程是一樣的,只是呼叫的函式不同而己,而函式,也可以當做參數。有了這層體認,我們就可以把這個呼叫兩次函式,然後印一段句子的過程抽象出來:
function cook(igr1, igr2, f) {
f(igr1)
f(igr2)
console.log(igr1 + " in " + igr2 + "is done!")
}
而想要真正調用魔法時:
cook("beef", "water", putInPot)
cook("chicken", "coconut", boomboom)
// 加上原先 putInPot 跟 boomboom 的實作
有注意到我們呼叫 cook
函式時的最後一個參數,putInPot
跟 boomboom
的後方沒有加上 ()
符號。那是因為在那裡我們並不想要調用這些函式,而是把函式本身當做參數傳給 cook
。真正調用這些函式的地方,在 cook
函式裡的 f(igr1)
跟 f(igr2)
那裡。
當然,我們也可以選擇不要把 putInPot
及 boomboom
這兩個函式分開來定義,而是直接寫進調用 cook
的過程裡:
cook("beef", "water", igr => console.log(igr + " in the pot!"))
cook("chicken", "coconut", igr => console.log("boil the " + igr + "!"))
這整段改寫的過程,讓我們更能體會這樣一句話:函式是計算過程的容器。
「可是小浣熊,你說的這些,我都曾經用過啊。那個匿名函式,不就是 callback
嗎?那些我早就知道了啊。」終於忍不住了。
我知道的。一直以來你都蠻會使用的,不然就不會召喚到你了。但是啊…蘋果,是水果、是食材、是果實,也是類似球狀的物體。差別在於觀點的不同。
而用不同的角度來理解同一件事,會讓我們獲得更為深刻的洞見。記得我們上次提到的三個特性嗎?如果沒有深刻的理解一等公民的概念,最後一個,也是最有威力的魔法,施展起來會非常不順手。
「什麼最後一個魔法?」
回傳函式的函式。
啊,還有,我不是浣熊。
[to be continued]
你是小貓熊
欸這留言壞了吧 還沒辦法刪的樣子
卡米狗不要這麼早就爆雷啊 XD
XDDDDDDDD
文章標題好酷澳
謝謝你!
昨天原本還看不懂這個用法跟callback的差異
剛剛騎車時忽然想到
所以callback不是一個function的return替代式
是將function寫入另一個function做呼叫呀
喔喔很接近了,callback 有個正式的稱呼叫 Continuation-passing style (CPS),是指某個函式裡會呼叫另一個函式,但是這個被呼叫的函式是用「參數」的方式傳進來的。
而這個參數可以另外定義,或是用變數的方式傳進來,也可以在呼叫的時候直接寫在呼叫當下。
用數字來看:
function addOne(i) {
return i + 1; //這裡用到了 i
}
let x = 100;
addOne(x); //可以像這兩行
addOne(100); //也可以像這樣
那麼函式也一樣,只要傳遞的參數是個函式,這兩種寫法都算是 callback
。但是用 functional 的方式想就不用糾結在這些名詞上了。反正函式可以當成參數,範例跟 map
, reduce
, filter
跟 sort
通通是同一種東西。
你好熱心,謝謝你